﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NConf.Interfaces;
using System.Threading;

namespace NConf.ConfigSources
{
    /// <summary>
    /// A configuration source backed by a simple dictionary.
    /// </summary>
    public class DictConfigSource : IConfigSource
    {
        //---------------------------------------------------------------------

        #region Properties

        /// <summary>
        /// A dictionary containing the configuration elements provided by this config source.
        /// </summary>
        public Dictionary<string, object> Configuration { get; set; }

        #endregion

        //---------------------------------------------------------------------

        #region Internal Members

        /// <summary>
        /// A Read/Write thread lock
        /// </summary>
        private ReaderWriterLockSlim confLock = new ReaderWriterLockSlim();

        #endregion

        //---------------------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Default Constructor.
        /// </summary>
        public DictConfigSource()
        {
            Configuration = new Dictionary<string, object>();
        } // end DummyConfigSource

        /// <summary>
        /// Default Constructor.
        /// </summary>
        /// <param name="sourceDict">The source Dictionary to wrap as a config source.</param>
        public DictConfigSource(Dictionary<string, object> sourceDict)
        {
            Configuration = sourceDict;
        } // end DummyConfigSource

        #endregion

        //---------------------------------------------------------------------

        #region IConfigSource Implementation

        /// <summary>
        /// Gets the value specified by config path, or throws an exception.
        /// </summary>
        /// <param name="configPath">A string represented the 'dotted' path to the configuration element desired.</param>
        /// <returns>The object specified by the config path.</returns>
        public virtual object GetValue(string configPath)
        {
            var pathElems = configPath.Split('.');

            // Start with our value equal to the entire config dictionary.
            object currentValue = Configuration;

            // Aquire a read lock
            confLock.EnterReadLock();
            try
            {
                // Iterate over the path elements we're looking for
                foreach (var elem in pathElems)
                {
                    // Attempt to recursively look up the value of the path.
                    currentValue = ((Dictionary<string, object>) currentValue)[elem];
                } // end foreach
            }
            finally { confLock.ExitReadLock(); }
                
            // Return whatever it is that we've looked up.
            return currentValue;
        } // end GetValue

        /// <summary>
        /// Gets the value specified by the config path, or returns the default value.
        /// </summary>
        /// <param name="configPath">A string represented the 'dotted' path to the configuration element desired.</param>
        /// <param name="defaultValue">The object to return if the desired object isn't found.</param>
        /// <returns>The object specified by the config path, or the default value.</returns>
        public object GetValue(string configPath, object defaultValue)
        {
            try
            {
                return GetValue(configPath);
            }
            catch
            {
                return defaultValue;
            } // end try/catch
        } // end GetValue

        /// <summary>
        /// Sets the given value specified by a key value pair.
        /// </summary>
        /// <param name="kvp">A key value pair of config path, and value.</param>
        public void SetValue(KeyValuePair<string, object> kvp)
        {
            SetValue(kvp.Key, kvp.Value);
        } // end SetValue

        /// <summary>
        /// Sets the config element specified by the path to the given value.
        /// </summary>
        /// <param name="configPath">The path to the element to set.</param>
        /// <param name="value">The value to set the element to.</param>
        public void SetValue(string configPath, object value)
        {
            //TODO: This will need to be done against the default writable config source, eventually

            var pathElems = configPath.Split('.');
            var memberToSet = pathElems.Last();

            // Start with our value equal to the entire config dictionary.
            object currentElem = Configuration;

            // Aquire a write lock
            confLock.EnterWriteLock();
            try
            {
                // Iterate over the path elements we're looking for
                foreach (var elem in pathElems)
                {
                    if (elem == memberToSet)
                    {
                        ((Dictionary<string, object>)currentElem)[elem] = value;
                    }
                    else
                    {
                        try
                        {
                            // Attempt to recursively look up the value of the path.
                            currentElem = ((Dictionary<string, object>)currentElem)[elem];
                        }
                        catch (InvalidCastException)
                        {
                            currentElem = new Dictionary<string, object>();
                        }
                        catch (KeyNotFoundException)
                        {
                            ((Dictionary<string, object>)currentElem)[elem] = new Dictionary<string, object>();
                            currentElem = ((Dictionary<string, object>)currentElem)[elem];
                        }
                        catch
                        {
                            // We've failed completely.
                            break;
                        } // end try/catch
                    } // end if
                } // end foreach
            }
            finally { confLock.ExitWriteLock(); }
        } // end SetValue

        /// <summary>
        /// Checks to see if the path specified has a value (including null), or is unset (or a dictionary).
        /// </summary>
        /// <remarks>
        /// This function returns true if a specific configuration value is set in the config. If the path specifies a
        /// dictionary or is unset, this function returns false.
        /// </remarks>
        /// <param name="configPath">The path to the configuration element we're testing.</param>
        /// <returns>Returns true if set, false otherwise.</returns>
        public bool HasValue(string configPath)
        {
            try
            {
                GetValue(configPath);
                return true;
            }
            catch
            {
                return false;
            } // end try/catch
        } // end HasValue

        /// <summary>
        /// Checks to see if the path specified contains other values (i.e. is a config section.)
        /// </summary>
        /// <param name="configPath">The path to the configuration element we're testing.</param>
        /// <returns>Returns true if the path has sub elements, false otherwise.</returns>
        public bool IsSection(string configPath)
        {
            return GetValue(configPath, null) is Dictionary<string, object>;
        } // end IsSection

        /// <summary>
        /// Generates a new ConfigKey from the given config path.
        /// </summary>
        /// <param name="configPath">The path to generate the ConfigKey for.</param>
        /// <returns>A new ConfigKey instance.</returns>
        public dynamic GetConfigKey(string configPath)
        {
            return new ConfigKey(configPath, this);
        } // end GetConfigKey

        #endregion

        //---------------------------------------------------------------------
    } // end DictConfigSource
} // end NConf.ConfigSources namespace
